import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.*;


/**
 * Represents a connection to a remote server
 * 
 * @author Ricky Vincent
 */
public class Connection implements Runnable {

	private static final String END_LINE = "\n";
	
	private Socket socket;
	private DataOutputStream out;
	private BufferedReader in;
	
	private boolean connected = false;
	
	private PlayerModel player;
	
	/**
	 * Creates a connection to a remote server. Sets up the output and input streams.
	 * 
	 * @param host The hostname of the server to connect to.
	 * @param port The port of the server to connect to.
	 * @throws IOException 
	 * @throws UnknownHostException 
	 */
	public Connection (String host, int port) throws UnknownHostException, IOException {
		socket = new Socket(host, port);
		out = new DataOutputStream(socket.getOutputStream());
		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		connected = true;
	}
	
	/**
	 * Receives data from the socket (and decrypts it?)
	 */
	public void receiveData() {
		
		String input = null;
		
		while(connected) {
			try {
				input = in.readLine();
				commandHandler(input);
				
			} catch (IOException e) {
				
				System.err.println("Lost connection to server... retrying");
				
				try {
					//sleep for 1 second between reconnection attempts
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					continue;
				}
			}
		}
	}
	
	
	/**
	 * Handles commands. Calls the correct handler methods.
	 * 
	 * @param command the command to handle.
	 */
	private void commandHandler (String command) {
		//print non move commands
		if(command.charAt(0) != Commands.CMD_MOVE_BY) {
// 			System.out.println("CLIENT RECEIVE: " + command);
		}
		
		switch (command.charAt(0)) {
			
		case Commands.CMD_UPDATE_ALL:
			updateCards(command.substring(1));
			break;
			
		case Commands.CMD_FLIP:
			flipCards(command.substring(1));
			break;
		
		case Commands.CMD_MOVE_BY:
			moveBy(command.substring(1));
			break;
			
	case Commands.CMD_NEW_DECK:
			getNewDeck(command.substring(1));
			break;
		}
	}

	
	public void run() {
		receiveData();		
	}
	
	private void getNewDeck(String cardsString) {
		String[] cards = cardsString.split(Commands.DEL);
		int pos = 0;
		
		Deck deck = player.getModel().getRoom().getTable().getDeck();
		Card tempCard;
		
		
		
		player.alert();
	}
	
	/**
	 * Flips cards as directed by server.
	 */
	private void flipCards(String flip) {
		String[] cards = flip.split(Commands.DEL);
		Deck deck = player.getModel().getRoom().getTable().getDeck();
		
		for(String id: cards) {
			deck.getCardFromID(Integer.parseInt(id)).toggleFaceUp();
		}
		player.alert();
	}
	
	private void moveBy(String args) {
		String[] cards = args.split(Commands.DEL);
		Deck deck = player.getModel().getRoom().getTable().getDeck();

		int x = Integer.parseInt(cards[0]);
		int y = Integer.parseInt(cards[1]);
		
		Set<Card> cds = new HashSet<Card>();
		
		for(int pos = 2; pos < cards.length; ++pos) {
			cds.add(deck.getCardFromID(Integer.parseInt(cards[pos])));
		}
		player.serverMoveBy(cds, x, y);
		player.alert();
	}
	
	
	/**
	 * Disconnects from the server.
	 */
	public void disconnect() {
		connected = false;
	}

	/**
	 * @return the player
	 */
	public PlayerModel getPlayer() {
		return player;
	}

	/**
	 * @param player the player to set
	 */
	public void setPlayer(PlayerModel player) {
		this.player = player;
		player.setPlayerListener(new PlayerData(player));
		player.alert();
	}
	
	private void updateCards(String cardsString) {
		String[] cards = cardsString.split(Commands.DEL);
		int pos = 0;
		
		Deck deck = player.getModel().getRoom().getTable().getDeck();
		Card tempCard;
		
		for(;pos < cards.length;) {
			tempCard = deck.getCardFromID(Integer.parseInt(cards[pos++]));
			tempCard.setLocation(Integer.parseInt(cards[pos++]), Integer.parseInt(cards[pos++]));
			tempCard.setFaceUp(cards[pos++].equals("1") ? true : false);
			tempCard.setZ(Integer.parseInt(cards[pos++]));
		}
		
		player.alert();
	}
	

	/**
	 * Writes bytes directly to the <code>Socket</code>.
	 * 
	 * @param data the array of bytes to write.
	 * @throws IOException
	 */
	private void sendData (byte[] data) throws IOException {
		out.write(data);
		out.flush();
	}
	
	
	/**
	 * Sends a command to this <code>Client</code>
	 * 
	 * @param command The command to send.
	 * @param args The arguments to send with the command.
	 * @throws IOException
	 */
	public void sendCommand(char command, String args) throws IOException {
		args = command + args + END_LINE;
		sendData(args.getBytes());
	}

	/**
	* Syncs this client to the server.
	*/
	public void syncToServer() throws IOException {
		String cardsString = "";
		
		for(Card card: player.getModel().getRoom().getTable().getDeck().getDeck()) {
			cardsString += card.getImmutableID() + Commands.DEL + card.getLocation().x + Commands.DEL + card.getLocation().y + Commands.DEL + 				 (card.isFaceUp() ? 1 : 0) + Commands.DEL + card.getZ() + Commands.DEL;
		}
		
		sendCommand(Commands.CMD_UPDATE_ALL, cardsString);
	}

}
